package com.tianyadu.mmall.core.validate.code.service;

import com.tianyadu.mmall.core.enums.ValidateCodeTypeEnum;
import com.tianyadu.mmall.core.validate.code.ValidateCode;
import com.tianyadu.mmall.core.validate.code.exception.ValidateCodeException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

import java.util.Map;

/**
 * 抽象的图片验证码处理器
 *
 * @author xueyj
 * @date 2019-11-14 00:10
 */
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
    /**
     * 操作session的工具类
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    /**
     * spring依赖搜索
     * 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现。
     * spring启动的时候，查找map的value，接口ValidateCodeGenerator的所有实现bean，
     * 并把这个bean为value，bean的名称为key存入map中
     */
    @Autowired
    private Map<String, ValidateCodeGenerator> validateCodeGenerators;

    /**
     * 创建校验码
     *
     * @param request spring工具类，封装请求/响应信息
     * @return void
     * @author xueyj
     * @date 2019/11/14-0:09
     * @see com.tianyadu.mmall.core.validate.code.service.ValidateCodeProcessor#create(org.springframework.web.context.request.ServletWebRequest)
     */
    @Override
    public void create(ServletWebRequest request) throws Exception {
        //  验证码生成
        C validateCode = generate(request);
        //  验证码存储
        save(request, validateCode);
        //  验证码发送
        send(request, validateCode);
    }

    /**
     * 生成校验码
     *
     * @param request spring工具类，封装请求/响应信息
     * @return C
     * @author xueyj
     * @date 2019/11/14-0:12
     */
    private C generate(ServletWebRequest request) {
        String type = getValidateCodeType(request).toString().toLowerCase();
        String generatorName = type + ValidateCodeGenerator.class.getSimpleName();
        ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName);
        if (validateCodeGenerator == null) {
            throw new ValidateCodeException("验证码生成器" + generatorName + "不存在");
        }
        return (C) validateCodeGenerator.generate(request);
    }

    /**
     * 保存校验码
     *
     * @param request      spring工具类，封装请求/响应信息
     * @param validateCode validateCode
     * @return void
     * @author xueyj
     * @date 2019/11/14-0:13
     */
    private void save(ServletWebRequest request, C validateCode) {
        sessionStrategy.setAttribute(request, getSessionKey(request), validateCode);
    }

    /**
     * 构建验证码放入session时的key
     *
     * @param request spring工具类，封装请求/响应信息
     * @return java.lang.String
     * @author xueyj
     * @date 2019/11/14-0:13
     */
    private String getSessionKey(ServletWebRequest request) {
        return SESSION_KEY_PREFIX + getValidateCodeType(request).toString().toUpperCase();
    }

    /**
     * 根据请求的url获取校验码的类型
     *
     * @param request spring工具类，封装请求/响应信息
     * @return ValidateCodeType
     * @author xueyj
     * @date 2019/11/14-0:14
     */
    private ValidateCodeTypeEnum getValidateCodeType(ServletWebRequest request) {
        String type = StringUtils.substringBefore(getClass().getSimpleName(), "ValidateCodeProcessor");
        return ValidateCodeTypeEnum.valueOf(type.toUpperCase());
    }

    /**
     * 发送校验码，由子类实现
     *
     * @param request      spring工具类，封装请求/响应信息
     * @param validateCode validateCode
     * @return void
     * @author xueyj
     * @date 2019/11/14-0:14
     */
    protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;

    /**
     * 校验验证码
     *
     * @param request spring工具类，封装请求/响应信息
     * @return void
     * @author xueyj
     * @date 2019/11/14-0:09
     */
    @Override
    public void validate(ServletWebRequest request) {
        //  获取验证码类型
        ValidateCodeTypeEnum processorType = getValidateCodeType(request);
        //  获取请求中sessionkey
        String sessionKey = getSessionKey(request);
        //  获取请求参数中传递验证码信息
        C codeInSession = (C) sessionStrategy.getAttribute(request, sessionKey);
        //  初始化请求中参数信息
        String codeInRequest;
        try {
            //  获取请求中传递参数信息
            codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
                    processorType.getParamNameOnValidate());
        } catch (ServletRequestBindingException e) {
            throw new ValidateCodeException("获取验证码的值失败");
        }
        //  若请求中imageCoe传递参数信息为空，抛出对应异常
        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException(processorType + "验证码的值不能为空");
        }
        //  若session中验证码信息为空，则抛出对应异常信息
        if (codeInSession == null) {
            throw new ValidateCodeException(processorType + "验证码不存在");
        }
        //  若验证码过期，则抛出对应异常，校验规则，当前时间是否在过期时间之前
        if (codeInSession.isExpried()) {
            //  若当前验证码过期，将其从session中移除
            sessionStrategy.removeAttribute(request, sessionKey);
            throw new ValidateCodeException(processorType + "验证码已过期");
        }
        //  校验session中存储验证码是否与参数中传递值一致，若不一致，则抛出相应异常
        if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
            throw new ValidateCodeException(processorType + "验证码不匹配");
        }
        //  若验证通过，将验证码信息从session中移除
        sessionStrategy.removeAttribute(request, sessionKey);
    }
}